简介
记录golang的程序结构相关知识。包括函数、包管理、基本的流程控制(for,defer,panic recover等等)。go语言中也存在函数参数的值传递,引用传递。
函数是对一系列语句打包的单元。
1.函数定义
1 | func name(parameter-list)(result-list) { |
能支持多返回值,或者无返回值。匿名函数是指的没有名字的函数。函数可以成为一个结构体字段,也能成为通道来传递。
匿名函数是一种常见的重构手段。可以将大函数分解成多个相对独立的匿名函数块,这样主干部分的调用函数将会更加简洁,做到框架和细节分离。
传递类型 | 描述 |
---|---|
值传递 | 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 |
引用传递 | 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 |
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
1 | /* 定义相互交换值的函数 */ |
值接收器(Value Receiver)方法
在方法定义时,使用结构体的实例作为接收器。这种方式可以在方法内部对结构体的字段进行读取操作,但无法修改结构体本身。例如:
1 | type MyStruct struct { |
指针接收器(Pointer Receiver)方法
在方法定义时,使用结构体的指针作为接收器。这种方式可以在方法内部对结构体的字段进行读取和修改操作,可以改变结构体本身。例如:
1 |
|
2.匿名函数
1 | func test(x int)func() { |
通过匿名函数将变量123缓存再函数中。最后调用的时候,可以很方便的将123输出。
警告:捕获迭代变量Go词法作用于陷阱。即使经验丰富程序员也会在这个问题上犯错误。
1 | var rmdirs []func() |
为啥要将d重新赋值成dir?原因就在循环变量中的作用域。如果直接使用d将会删除的相同的目录。这样的问题在使用defer语句也有相同问题。
3.延迟调用
defer语句是指注册一个函数,再稍后调用。一般用于资源释放,解锁,错误处理等操作。
1 | func main () { |
延时调用遵循FILO,堆栈式的调用。简单理解就是先进后出。
错误的用法
1 | func main() { |
这种情况,应该将操作文件的部分封装成小函数,避免再循环过程中,直接使用defer。
性能方面,defer方式需要花掉比较大的代价。
4.错误处理
调侃go的人,说错误处理有些返祖。”stuck in 70’s”。
提供了error的接口。err := errors.New("division by zero")
这样就能构造一个出来。有经验的程序员一般都会对错误做一些预计的处理。函数如果返回类型为error时候,都需要判断一下错误信息是否为空。
5.panic recover
类似于exception/try/catch结构化异常。函数原型如下。
1 | func panic(v interface{}) |
他们是内置函数,不是语句。panic将会终端当前流程(如:数组访问越界、空指针引用),并且触发defer的调用。在延时调用中可以通过recover捕获其中的报错。
1 | func main() { |
recover必须在panic触发之后才能捕获到错误,而且只能在延时调用函数中才能工作。不能在函数内部直接使用。
1 | func catch() { |
调试期间可以使用 runtime/debug.PrintStack。获取完整的堆栈信息。将会输出到标准错误日志中。
1 | // PrintStack prints to standard error the stack trace returned by runtime.Stack. |
使用panic的时候,建议是遇到了不可恢复、导致系统无法运行的错误,否则不要使用。服务器端口占用、文件无法打开、数据库未启动。这种错误超出了程序员的控制的。程序必须停止的情况。
6.错误处理策略
- 将错误信息上报给调用者,并且添加额外的上下文信息;错误信息最好能描述出问题的需要的参数;
- 如果错误是偶发的,或者是不可预计问题导致。我们需要构造有限次数的重试机制,避免无限制的重试;
- 出现错误之后,程序其实已经无法执行,那就需要将错误信息输出出来,并且将程序终止;避免出现更多的问题。比如数据库已经无法写入了,机器的硬盘已经无法写入文件内容了;
- 错误不致命,只需要在错误级别日志中输出,然后继续执行程序;
- 可以忽略掉一些无关紧要的错误。
在Go中,函数被当作第一类型(first-class values),函数像其他值一样,拥有类型,可以传送,赋值给其他变量。函数值不能做比较,所以不能当成map的key。
7.流程控制
1.for statements
查看文档:
1 | // file:c:/go/doc/go_spec.html#For_statements |
标准目录为src、bin、pkg三个目录。
GOPATH可以指定几个目录,排在列表最前面的比当前工作空间优先级更高。go get默认会下载到第一个工作空间里面。备注:unix-like使用冒号分隔,windows使用;分割。
GOROOT指定工具链和标准库的存放位置。
2. switch statements
1 | package main |
fallthrough/[break|continue] label
这个语法可能容易被忽略,fallthrough就是让switch语句漏下去。
[break|continue] label在多重循环嵌套的时候将会使用。
1 | package main |
8.导入包
如果是系统级的包的导入
1 | import "net/http" |
9.组织结构
包由一个或多个保存在同一目录下的源文件组成。
包名和目录名称并无关系,不要求保持一致,举个例子,leaf里面的go的库。目录名为go,但是里面的包使用的名称为g。不影响使用,在import的时候是写go,语句里面写g。
1 | "github.com/name5566/leaf/go" |
同一目录下的包名需要完全一致。下列有几个特殊的包名:
main :可执行入口
all: 标准库以及 GOPATH中能找到的所有包。
std,cmd:标准库工具链
documentations:存储文档信息、无法导入。
10.权限
包内成员可以互相访问。只有大写字母的能被包外访问。当然也能通过unsafe.Pointer方式来反出数据并且调用。
11.初始化
1 | func init() { |
12.内部包
将内部模块分离出来,单独包的形式维护。只能被父目录下的包访问,命名为internal目录下的包。
13.依赖管理
主要是解决三方依赖库文件冲突问题。这个概念是和内部包刚好相反,vendor目录是提供给当前工作目录,通用的目录。
注意:vendor比标准库有嫌隙更高。
14.程序的目录建议
1 | --- |
mgr.go 用于放置提供给外部调用函数;外部需要使用的类;
ixxx 目录放置模块的接口定义;
models 目录放置实现函数;
图中说明了绘制了依赖关系。这样能防止再外面循环引用的问题:
defer详解
1 |
go map[string]string 排序
1 | func main() { |
vip
退出服务器的方法
1 | // 等待输入回车关闭 |
gin 服务器如何退出来;
1 | // +build go1.8 |
在 Go 中,可以使用数组来初始化一组 struct 类型的对象。以下是一个示例,展示了如何初始化一个包含多个 struct 对象的数组。
假设我们有一个 Person 结构体:
type Person struct {
Name string
Age int
}
方法 1:直接初始化数组
可以在声明数组时直接初始化每个 struct 实例:
package main
import “fmt”
type Person struct {
Name string
Age int
}
func main() {
// 初始化一个固定长度的结构体数组
people := [3]Person{
{Name: “Alice”, Age: 25},
{Name: “Bob”, Age: 30},
{Name: “Charlie”, Age: 35},
}
fmt.Println(people)
}
方法 2:使用索引进行赋值
可以先声明数组,然后使用索引给每个 struct 成员赋值:
func main() {
// 声明一个长度为 3 的结构体数组
var people [3]Person
// 为数组中的每个元素赋值
people[0] = Person{Name: "Alice", Age: 25}
people[1] = Person{Name: "Bob", Age: 30}
people[2] = Person{Name: "Charlie", Age: 35}
fmt.Println(people)
}
方法 3:如果数组大小不固定,使用切片
如果不确定数组的大小,可以使用切片来存储 struct 对象:
func main() {
// 使用切片初始化结构体对象数组
people := []Person{
{Name: “Alice”, Age: 25},
{Name: “Bob”, Age: 30},
{Name: “Charlie”, Age: 35},
}
fmt.Println(people)
}
在以上示例中,people 切片可以动态扩展,适合需要添加或删除元素的场景。
这些方法都可以初始化一个 struct 类型的数组或切片,根据需求选择适合的方式即可。